Compound Miters

by John Taylor

This is a notebook for woodworkers and carpenters.  Compound angles are frequently  the bane of such craftsmen. Examples are installation of crown molding at corners other than 90 degrees, constructing skylights at the junction of pitched roofs,and the design of flower pots with tapered sides.  Given the results of the calculations,  the next problem is sufficiently  precise  execution. Ordinary shop equipment is not designed for the level of precision sometimes required  to produce acceptably tight joints.
    
From  The Merchant of Venice: If to do were as easy as to know what were good to be done, chapels would be churches and poor men's cottages princely palaces.
    
The problem to be addressed is the calculation of miter and bevel angles for joining  pieces of plane stock at arbitrary angles .  In particular, the issue is how to cut the miters and bevels on all edges when three sides of a box join at a point O, with edges \(OA\&_\), \(OB\&_\), \(OC\&_\) making arbitrary angles AOB,BOC,COA with each other.  The problem is further complicated by the possibility that the input data may be the angles between the planes rather than the angles between the edges, and that the stock may not be of equal thicknesses.  Figure 1 is a sketch of the geometry of the problem.

The angles P1,P2,P3 are the angles between the planes defined by the line pairs {AOB,BOC},{BOC,COA},{COA,AOB}. They determine the bevel angles in the construction cuts. In the usual terminology, these angles are 90 degrees greater than the splay angles, which measure the excess of the angles beyond 90 degrees.   The plane AOB is defined as the base plane.

The sets of angles of interest in fabrication operations are the miter and bevel angles on the pieces of stock used to fabricate the structure.  The miter and bevel  angles are also specified as departures from 90 degrees.As a further complication, if the pieces of stock are not of the same thickness, the bevel angles on two adjacent pieces are unequal. Figure 2 shows the geometry.  The thickness of the two pieces of stock are  (Subscript[T, 1]) and (Subscript[T, 2]).  The dotted line is the joining line of the  two pieces of stock and the solid lines define the perpendiculars from the sides of the stock.  The angle P is the angle between the planes.

\(The\ two\ bevel\ angles\ \(B\_1\) 
      and\ B\_2\ are\ related\ to\ the\ plane\ angle\ P\ by\ the\ relation\ 
\)
\(					B\_1 + B\_2 = 
      180 - P\ \ \ \ \ \ \((1)\)
\)
We also have the relation 

\(					\(T\_2\) Cos[B\_1] = \(T\_1\) 
        Cos[B\_2], \ \ \((2)\)
\)
From these relations we find that

\(					Cos[
        B\_2] = \ \(x\ Sin[P]\)\/\@\(1 + x\^2 + 2\ x\ Cos[P]\), \ \((3)\)
\)
\(					Cos[B\_1] = 
      Sin[P]\/\@\(1 + x\^2 + 2\ x\ Cos[P]\), \ \((4)\)
\)
where  
\(					x = \(T\_2\/T\_1\) . \ \ \((5)\)\)

With this note, we proceed to determine the required angles from the input data, which may vary depending on the design problem at hand. 
    
Four possible sets of input data are supported.  (1) A base angle and two splay angles. The function is called cuttingangles.  (2)  Three edge angles. The function is called planeangles from what it calculates. (3)  Three plane angles. The function is called edgeangles from what it calculates. (4) A regular polygon is specified by the number n of sides, and the splay angles for all sides is specified.  The miters and bevels on all sides are computed.  All four functions  give bevel angles for equal stock thicknesses as output, in addition to the input.  A fifth function, bevelsangles, takes  as input two stock thicknesses  in either{inch, decimal} or {inch, fraction} format , plus a plane angle and outputs  bevels for the two thicknesses. 

The computational cell properties are  set to Editable->False to protect the user from extinguishing the value of the program unless he/she deliberately decides to modify it.

There are two conditions that,if violated, produce error messages. 
(1) The sum of the three plane angles must be at least 180 degrees.
(2) The largest edge angle must be less than  the sum of the two other edge angles.

All four main  computational cells are Hyperlinked to the bevelsangles cell so that it is easy to find where to calculate for different stock thicknesses whenever necessary.  The following Hyperlinks send you to the different computational cells.  The home hyperlink, available after each module,  returns you to the beginning of the introduction.

cuttingangles
planeangles
edgeangles
regularpolygon
bevelsangles
splay

Most commonly, the base plane angle AOB is a given and the splay angles for the planes COA and BOC are given.

We have 
\(				splay\_1 = P\_3 - 90, \((6)\)\)
\(				splay2 = P\_1 - 90.  \((7)\)\)

The angles  desired are ∠BOC and ∠COA which determine the miters on the edges of the two pieces of stock that join on the line\(\(\(\ \)\(OC\&_\)\)\) and the angle (Subscript[P, 2 ])which determines the bevels on the stock along this line (see Fig. 2).  In the input to the module, the angle AOB is called the baseangle. The module is cuttingangles.

\(cuttingangles[splay1_, splay2_, baseangle_] := 
      Module[{P, m1, m2, plane, bevel}, 
        P := ArcCos[
            Cos[splay1\ Degree]\ Cos[splay2\ Degree]\ Cos[
                  baseangle\ Degree] - 
              Sin[splay1\ Degree]\ Sin[splay2\ Degree]]; 
        m1 = N[ArcSin[\((Sin[splay2\ Degree] + 
                      Sin[splay1\ Degree]\ Cos[P\ ])\)/\((Cos[
                        splay1\ Degree]\ Sin[P])\)]/Degree]; 
        m2 = N[ArcSin[\((Sin[splay1\ Degree] + 
                      Sin[splay2\ Degree]\ Cos[P\ ])\)/\((Cos[
                        splay2\ Degree]\ Sin[P])\)]/
              Degree]; 
plane := 
          N[P/Degree]; 
bevel := \((180 - plane)\)/
            2; 
TableForm[
          Table[{{"BaseAngle", "" <> 
                  ToString[
                    baseangle] <> "°", \ "PlaneAngle", "" <> ToString[plane] <> "°", "BevelAngles", "" <> 
                  ToString[bevel] <> "°"}, {"Splay1", "" 
                  ToString[
                      splay1] <> "°", "MiterCOA", "" <> 
                  ToString[m1] <> "°"}, {"Splay2", "" <> 
                  ToString[
                    splay2] <> "°", "MiterBOC ", "" <> 
                  ToString[m2] <> "°"}}]]]\)

bevelsangles
home

The bevelsangles module calculates the two bevel angles for unequal stock thicknesses, given the thicknesses and the plane angle.   It outputs the bevels and the decimal form of the input thicknesses.  Input thicknesses are given in inches and either decimal (or whole number fractions in the list  format {inch,fraction}).  The plane angle is input in degrees, with decimal fractions if appropriate. This module must be regarded as a supplement to planeangles and edgeangles if the stock thicnesses are unequal, and calculated using the proper thicknesses and the given or calculated plane angles associated with the appropriate edges.

\(bevelsangles[thickness1_List, thickness2_List, P_] := 
      Module[{c, d, nfunc, x}, 
nfunc[x_List] := 
          N[x[\([1]\)] + x[\([2]\)]]; c = nfunc[thickness1]; 
        d = nfunc[thickness2]; x = c/d; 
        TableForm[
          Table[{{"Plane Angle", "" <> 
                  ToString[P] <> "°"}, {"Thickness1", 
                c, "Bevel1", 
                N[ArcCos[\(x\ Sin[P\ Degree]\)\/\@\(1 + x\^2 + 2\ x\ Cos[P\ Degree]\)]/Degree]}, {"Thickness2", d, "Bevel2", 
                N[ArcCos[
                      Sin[P\ Degree]\/\@\(1 + x\^2 + 2\ x\ Cos[P\ Degree]\)]/
                    Degree]\ }}]]]\)

Another possible set of input data is the set of edge angles {AOB,BOC,COA}.  All miter angles are then determined, but no bevel angles are known.  For this problem we need  to calculate the set {P1,P2,P3}.  This is done by planeangles.  . 
Note:  If the two pieces of stock are of equal thickness, the bevel angles are given by 

				B=(180-P)/2.
				
If they are of unequal thickness, you must use the bevelsangles module.

The inputs to the planeangles function are the edge angles.  The sum of the two smaller angles must at least equal the larger angle.

\(planeangles[aob_, boc_, coa_] := 
      Module[{p1, p2, p3, planeangles}, 
        edgeangles::"TooSmall" := "Sum of two smaller angles must be at least equal to largest angle"; 
If[
          2\ Max[aob, boc, coa] <= Apply[Plus, {aob, boc, coa}], 
          p1 := N[ArcCos[\((Cos[coa\ Degree] - 
                        Cos[aob\ Degree] Cos[boc\ Degree])\)/\((Sin[
                          aob\ Degree]\ Sin[boc\ Degree])\)]]/Degree; 
          p2 := N[ArcCos[\((Cos[aob\ Degree] - 
                        Cos[coa\ Degree] Cos[boc\ Degree])\)/\((Sin[
                          coa\ Degree]\ Sin[boc\ Degree])\)]]/Degree; 
          p3 := N[ArcCos[\((Cos[boc\ Degree] - 
                        Cos[aob\ Degree] Cos[coa\ Degree])\)/\((Sin[
                          aob\ Degree]\ Sin[coa\ Degree])\)]]/
              Degree; 
TableForm[
            Table[{{"              ∠AOB", "" <> 
                    ToString[
                      aob] <> "°", "∠BOC", "" <> 
                    ToString[
                      boc] <> "°", "∠COA", "" <> 
                    ToString[
                      coa] <> "°"}, {"              ∠P1", "" <> 
                    ToString[
                      p1] <> "°", "∠P2", "" <> 
                    ToString[
                      p2] <> "°", "∠P3", "" <> 
                    ToString[
                      p3] <> "°"}, {\*""              Bevels \!\(OB\&_\)"", "" <> 
                    ToString[\((\(-p1\) + 180)\)/
                        2] <> "°", \*""Bevels \!\(OC\&_\)"", "" <> 
                    ToString[\((\(-p2\) + 180)\)/
                        2] <> "°", \*""Bevels \!\(OA\&_\)"", "" <> 
                    ToString[\((\(-p3\) + 180)\)/
                        2] <> "°"}, {"Bevels assumes equal stock
thicknesses.If unequal
use bevelsangles function."\ }}]], 
          Message[edgeangles::"TooSmall"]]]\)

bevelsangles
home

The inverse problem arises when all three plane angles are known and the edge angles must be calculated.  In this case, the bevels are known and the miters must be determined.  This is done by edgeangles..  The sum of the plane angle inputs  must be at least 180 degrees.  Remember that the bevels output assumes equal stock thicknesses.  If they are unequal, you must use the bevelsangles function.

The miters are given by Miter=edgeangle-90.

\(edgeangles[P1_, P2_, P3_] := 
      Module[{aob, boc, coa, edgeangles}, 
        planeangles::"TooSmall" := "Sum of plane angles must be at least 180 Degrees"; 
If[
          Apply[Plus, {P1, P2, P3}] ≥ 180, 
          aob := N[
                ArcCos[\((Cos[P2\ Degree] + 
                        Cos[P1\ Degree]\ Cos[P3\ Degree])\)/\((Sin[
                          P1\ Degree]\ Sin[P3\ Degree])\)]]/Degree; 
          boc := N[
                ArcCos[\((Cos[P3\ Degree] + 
                        Cos[P2\ Degree]\ Cos[P1\ Degree])\)/\((Sin[
                          P2\ Degree]\ Sin[P1\ Degree])\)]]/Degree; 
          coa := N[
                ArcCos[\((Cos[P1\ Degree] + 
                        Cos[P2\ Degree]\ Cos[P3\ Degree])\)/\((Sin[
                          P2\ Degree]\ Sin[P3\ Degree])\)]]/Degree; 
          TableForm[
            Table[{{" P1", "" <> 
                    ToString[P1] <> "°", "P2", "" <> 
                    ToString[P2] <> "°", "P3", "" <> 
                    ToString[
                      P3] <> "°"}, {" ∠AOB", "" <> 
                    ToString[
                      aob] <> "°", "∠BOC", "" <> 
                    ToString[
                      boc] <> "°", "∠COA", "" <> 
                    ToString[
                      coa] <> "°"}, {\*"" Bevels \!\(OB\&_\)"", "" <> ToString[\((\(-P1\) + 180)\)/
                        2] <> "°", \*""Bevels \!\(OC\&_\)"", "" <> 
                    ToString[\((\(-P2\) + 180)\)/
                        2] <> "°", \*""Bevels \!\(OA\&_\)"", "" <> 
                    ToString[\((\(-P3\) + 180)\)/2] <> "°"}}]], 
          Message[planeangles::"TooSmall"]]]\)

bevelsangles
home

A fourth module, regularpolygon, seems appropriate because it arises in the  construction of a flower pot or tub.  The assumption is that the tub has an integral number n of sides and the sides are all splayed at an angle splay.  If the input n is not an integer there is an error message.

regularpolygon[n_, splay_] := 
      Module[{base, m, P, baseangle, sides}, 
        sides::"NotInteger" := "The number of sides must be an \
integer"; 
        If[IntegerQ[n], base :=  Pi (1 - 2/n); 
          m := N[ArcSin[
                  Sin[splay Degree] (1 +  
                          Cos[P])/(Cos[splay Degree] Sin[P])]/
                Degree]; 
P := 
            ArcCos[Cos[splay Degree]^2  Cos[base ] - 
                Sin[splay Degree]^2]; plane := N[P/Degree]; 
          bevel := (180 - plane)/2; baseangle := N[base/Degree]; 
          TableForm[
            Table[{{"Sides", "" <> 
                    ToString[n] <> ""}, {"Splay", "" <> 
                    ToString[
                      NumberForm[splay, 
                        4]] <> "°"}, { "Bevels", "" <> 
                    ToString[
                      NumberForm[bevel, 
                        4]] <> "°"}, {"Miters", "" <> 
                    ToString[
                      NumberForm[m, 
                        4]] <> "°"}, {"BaseAngle", "" <> 
                    ToString[
                      NumberForm[baseangle, 4]] <> "°"}}]], 
          Message[sides::"NotInteger"]]]

bevelsangles
home

regularpolygon2[n_, splay_] := 
      Module[{base, m, P, baseangle, sides}, 
        sides::"NotInteger" := "The number of sides must be an \
integer"; 
        If[IntegerQ[n], base :=  Pi (1 - 2/n); 
          m := N[ArcSin[
                  Sin[splay Degree] (1 +  
                          Cos[P])/(Cos[splay Degree] Sin[P])]/
                Degree]; 
P := 
            ArcCos[Cos[splay Degree]^2  Cos[base ] - 
                Sin[splay Degree]^2]; plane := N[P/Degree]; 
          bevel := (180 - plane)/2; baseangle := N[base/Degree]; 
          TableForm[
            Table[{{"Sides", "" <> 
                    ToString[n] <> ""}, {"Splay", "" <> 
                    ToString[
                      NumberForm[splay, 
                        4]] <> "°"}, { "Bevels", "" <> 
                    ToString[
                      NumberForm[bevel, 
                        4]] <> "°"}, {"Miters", "" <> 
                    ToString[
                      NumberForm[m, 
                        4]] <> "°"}, {"BaseAngle", "" <> 
                    ToString[
                      NumberForm[baseangle, 4]] <> "°"}}]], 
          Message[sides::"NotInteger"]]]

Splay angles may be detemined by protractor measurement, by fiat, or by specifying the diameters of the inscribed  circles  for both the top and bottom of the tub plus the height of the tub.   If the latter is used, three dimensions {smalldia, largedia, height} are needed as input.

home

Problem:  Design a tub that will permit insertion of a flower pot of conic shape.  The diameter of the base of the pot is smalldia.  The diameter of the top is largedia.  The height of the pot is height.  The intent is that the top of tub will be topoverhang inches above the top of the pot .  The thickness of the base  is  basethick inches and the staves of the tub should protrude bottomoverhang inches below the bottom of the base.  Calculate the splay angles, the lengths of the staves, the miters and bevels and the inside widths of the staves at the top and bottom.  The tub is to have N sides, where N is probably 6,8,10 or 12.  For ease of assembly, it is sometimes convenient to have a temporary top form to insert while working.

\(flowertub[n_, smalldia_, largedia_, height_, basethick_, wallthick_, 
        topoverhang_, bottomoverhang_, insert_] := 
      Module[{x1, x2, d1, d2, h, splay, baseangle, baseth, wallth, base, 
          miter, bevel, width, sides, P, basediam, splayout, insert1, 
          insertcut, formdia}, insert1 := N[insert]; x2 := topoverhang; 
        x1 := bottomoverhang; h := height; baseth := N[basethick]; 
        wallth := N[wallthick]; d1 := smalldia; d2 := largedia; 
        splay := ArcTan[\((d2 - d1)\)/h]; 
        splayout := 
          N[splay/Degree]; 
sides::"NotInteger" := "The number of sides must be an integer"; 
        If[IntegerQ[n], base := \ Pi \((1 - 2/n)\); 
          miter := N[
              ArcSin[Sin[
                      splay\ ] \((1 + \ Cos[P])\)/\((Cos[splay\ ]\ Sin[P])\)]/
                Degree]; 
P := 
            ArcCos[Cos[splay\ ]^2\ \ Cos[base\ ] - Sin[splay\ ]^2]; 
          plane := N[P/Degree]; bevel := \((180 - plane)\)/2; 
          baseangle := N[base/Degree]; 
          boardlength := \((h + x1 + x2 + baseth)\)/
              Cos[splay]; 
boardwidth := \((d2 + 
                  2\ wallth/Cos[splay])\) Tan[Pi/n]; 
          basediam := N[\((d1 + insert1)\)/Cos[Pi/n]]; 
          insertcut := \((baseth + x1)\)/Cos[splay]; 
          formdia := 
            N[\((largedia + x2\ Tan[splay])\)/
                Cos[splay]]; 
TableForm[
            Table[{{"InputData"}, {}, {"Sides", "" <> 
                    ToString[n] <> ""}, {"PotBaseDia", "" <> 
                    ToString[
                      smalldia] <> " Inch"}, {"PotTopDia", "" <> 
                    ToString[
                      largedia] <> " Inch"}, {"PotHeight", "" <> 
                    ToString[
                      height] <> " Inch"}, {"TubBaseThick", "" <> 
                    ToString[
                      baseth] <> " Inch"}, {"TubWallThick", "" <> 
                    ToString[
                      wallth] <> " Inch"}, {"BaseInsert", "" <> 
                    ToString[
                      insert1] <> " Inch"}, {"Topoverhang", "" <> 
                    ToString[
                      x2] <> " Inch"}, {"Bottomoverhang", "" <> 
                    ToString[
                      x1] <> " Inch"}, {}, {"OutputData"}, {}, {"SplayAngle", "" <> 
                    ToString[
                      NumberForm[splayout, 
                        4]] <> "°"}, {\ "Bevels", "" <> 
                    ToString[
                      NumberForm[bevel, 
                        4]] <> "°"}, {"Miters", "" <> 
                    ToString[
                      NumberForm[miter, 
                        4]] <> "°"}, {"BaseAngle", "" <> 
                    ToString[
                      baseangle] <> "°"}, {"BoardLength", "" <> ToString[NumberForm[boardlength, 
                        4]] <> " Inch"}, {"BoardWidth", "" <> 
                    ToString[
                      NumberForm[boardwidth, 
                        4]] <> " Inch"}, {"InsertCut", "" <> 
                    ToString[
                      NumberForm[insertcut, 
                        4]]\  <> " Inch"}, {"BaseDiam", "" <> 
                    ToString[
                      NumberForm[basediam, 
                        4]] <> " Inch"}, {"FormDiam", "" <> 
                    ToString[NumberForm[formdia, 5]] <> " Inch"}}]], 
          Message[sides::"NotInteger"]]

]\)

Here is an example of use of flowertub.

flowertub[8, 8, 10, 12, 3/4, 3/4, 1, 1.5, 1/8]